From: Tim Starling Date: Sun, 13 Aug 2006 00:49:32 +0000 (+0000) Subject: Revamped Special:Imagelist. Implemented generic table-based data display. X-Git-Tag: 1.31.0-rc.0~56010 X-Git-Url: http://git.cyclocoop.org/%7D%7Cconcat%7B?a=commitdiff_plain;h=d7b6ad563c2a846f9f9f9b9e2f7639f18e46daed;p=lhc%2Fweb%2Fwiklou.git Revamped Special:Imagelist. Implemented generic table-based data display. --- diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b299b41781..cbfc67f4c0 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -122,6 +122,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN use User::isAllowed instead. * (bug 769) OutputPage::permissionRequired() should suggest groups with the needed permission * (bug 6971) Fix regression in Special:Export history view +* Revamped Special:Imagelist == Languages updated == diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 24ac7cd44c..fb6dcea71b 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -116,6 +116,7 @@ function __autoload($className) { 'PageHistory' => 'includes/PageHistory.php', 'IndexPager' => 'includes/Pager.php', 'ReverseChronologicalPager' => 'includes/Pager.php', + 'TablePager' => 'includes/Pager.php', 'Parser' => 'includes/Parser.php', 'ParserOutput' => 'includes/Parser.php', 'ParserOptions' => 'includes/Parser.php', diff --git a/includes/Pager.php b/includes/Pager.php index db062de77b..e882a36325 100644 --- a/includes/Pager.php +++ b/includes/Pager.php @@ -16,8 +16,7 @@ interface Pager { * * ReverseChronologicalPager is a child class of the abstract IndexPager, and contains * some formatting and display code which is specific to the use of timestamps as - * indexes. It is currently the only such child class. Here is a synopsis of the operation - * of IndexPager and its primary subclass: + * indexes. Here is a synopsis of its operation: * * * The query is specified by the offset, limit and direction (dir) parameters, in * addition to any subclass-specific parameters. @@ -212,6 +211,8 @@ abstract class IndexPager implements Pager { $s .= $this->formatRow( $row ); } } + } else { + $s .= $this->getEmptyBody(); } $s .= $this->getEndBody(); return $s; @@ -244,6 +245,14 @@ abstract class IndexPager implements Pager { return ''; } + /** + * Hook into getBody(), for the bit between the start and the + * end when there are no rows + */ + function getEmptyBody() { + return ''; + } + /** * Title used for self-links. Override this if you want to be able to * use a title other than $wgTitle @@ -289,6 +298,68 @@ abstract class IndexPager implements Pager { return $this->mResult->numRows(); } + /** + * Get a query array for the prev, next, first and last links. + */ + function getPagingQueries() { + if ( !$this->mQueryDone ) { + $this->doQuery(); + } + + # Don't announce the limit everywhere if it's the default + $urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit; + + if ( $this->mIsFirst ) { + $prev = false; + $first = false; + } else { + $prev = array( 'dir' => 'prev', 'offset' => $this->mFirstShown, 'limit' => $urlLimit ); + $first = array( 'limit' => $urlLimit ); + } + if ( $this->mIsLast ) { + $next = false; + $last = false; + } else { + $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); + $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); + } + return compact( 'prev', 'next', 'first', 'last' ); + } + + /** + * Get paging links. If a link is disabled, the item from $disabledTexts will + * be used. If there is no such item, the unlinked text from $linkTexts will + * be used. Both $linkTexts and $disabledTexts are arrays of HTML. + */ + function getPagingLinks( $linkTexts, $disabledTexts = array() ) { + $queries = $this->getPagingQueries(); + $links = array(); + foreach ( $queries as $type => $query ) { + if ( $query !== false ) { + $links[$type] = $this->makeLink( $linkTexts[$type], $queries[$type] ); + } elseif ( isset( $disabledTexts[$type] ) ) { + $links[$type] = $disabledTexts[$type]; + } else { + $links[$type] = $linkTexts[$type]; + } + } + return $links; + } + + function getLimitLinks() { + global $wgLang; + $links = array(); + if ( $this->mIsBackwards ) { + $offset = $this->mPastTheEndIndex; + } else { + $offset = $this->mOffset; + } + foreach ( $this->mLimitsShown as $limit ) { + $links[] = $this->makeLink( $wgLang->formatNum( $limit ), + array( 'offset' => $offset, 'limit' => $limit ) ); + } + } + /** * Abstract formatting function. This should return an HTML string * representing the result row $row. Rows will be concatenated and @@ -330,49 +401,236 @@ abstract class ReverseChronologicalPager extends IndexPager { if ( isset( $this->mNavigationBar ) ) { return $this->mNavigationBar; } - if ( !$this->mQueryDone ) { - $this->doQuery(); + $linkTexts = array( + 'prev' => wfMsgHtml( "prevn", $this->mLimit ), + 'next' => wfMsgHtml( 'nextn', $this->mLimit ), + 'first' => wfMsgHtml('histlast'), + 'last' => wfMsgHtml( 'histfirst' ) + ); + + $pagingLinks = $this->getPagingLinks( $linkTexts ); + $limitLinks = $this->getLimitLinks(); + $limits = implode( ' | ', $limitLinks ); + + $this->mNavigationBar = "($latestText | $earliestText) " . wfMsgHtml("viewprevnext", $prevText, $nextText, $limits); + return $this->mNavigationBar; + } +} + +/** + * Table-based display with a user-selectable sort order + */ +abstract class TablePager extends IndexPager { + var $mSort; + var $mCurrentRow; + + function __construct() { + global $wgRequest; + $this->mSort = $wgRequest->getText( 'sort' ); + if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) { + $this->mSort = $this->getDefaultSort(); } + if ( $wgRequest->getBool( 'asc' ) ) { + $this->mDefaultDirection = false; + } elseif ( $wgRequest->getBool( 'desc' ) ) { + $this->mDefaultDirection = true; + } /* Else leave it at whatever the class default is */ - # Don't announce the limit everywhere if it's the default - $urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit; - - if ( $this->mIsFirst ) { - $prevText = wfMsgHtml("prevn", $this->mLimit); - $latestText = wfMsgHtml('histlast'); - } else { - $prevText = $this->makeLink( wfMsgHtml("prevn", $this->mLimit), - array( 'dir' => 'prev', 'offset' => $this->mFirstShown, 'limit' => $urlLimit ) ); - $latestText = $this->makeLink( wfMsgHtml('histlast'), - array( 'limit' => $urlLimit ) ); + parent::__construct(); + } + + function getStartBody() { + global $wgStylePath; + $s = "\n"; + $fields = $this->getFieldNames(); + foreach ( $fields as $field => $name ) { + if ( strval( $name ) == '' ) { + $s .= "\n"; + } elseif ( $this->isFieldSortable( $field ) ) { + $query = array( 'sort' => $field, 'limit' => $this->mLimit ); + if ( $field == $this->mSort ) { + # This is the sorted column + # Prepare a link that goes in the other sort order + if ( $this->mDefaultDirection ) { + # Descending + $image = 'Arr_u.png'; + $query['asc'] = '1'; + $query['desc'] = ''; + $alt = htmlspecialchars( wfMsg( 'descending_abbrev' ) ); + } else { + # Ascending + $image = 'Arr_d.png'; + $query['asc'] = ''; + $query['desc'] = '1'; + $alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) ); + } + $image = htmlspecialchars( "$wgStylePath/common/images/$image" ); + $link = $this->makeLink( + "\"$alt\"" . + htmlspecialchars( $name ), $query ); + $s .= "\n"; + } else { + $s .= '\n"; + } + } else { + $s .= '\n"; + } } - if ( $this->mIsLast ) { - $nextText = wfMsgHtml( 'nextn', $this->mLimit); - $earliestText = wfMsgHtml( 'histfirst' ); - } else { - $nextText = $this->makeLink( wfMsgHtml( 'nextn', $this->mLimit ), - array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ) ); - $earliestText = $this->makeLink( wfMsgHtml( 'histfirst' ), - array( 'dir' => 'prev', 'limit' => $urlLimit ) ); + $s .= "\n"; + return $s; + } + + function getEndBody() { + return '
 $link' . $this->makeLink( htmlspecialchars( $name ), $query ) . "' . htmlspecialchars( $name ) . "
'; + } + + function getEmptyBody() { + $colspan = count( $this->getFieldNames() ); + $msgEmpty = wfMsgHtml( 'table_pager_empty' ); + return "$msgEmpty\n"; + } + + function formatRow( $row ) { + $s = "\n"; + $fieldNames = $this->getFieldNames(); + $this->mCurrentRow = $row; # In case formatValue needs to know + foreach ( $fieldNames as $field => $name ) { + $value = isset( $row->$field ) ? $row->$field : null; + $formatted = strval( $this->formatValue( $field, $value ) ); + if ( $formatted == '' ) { + $formatted = ' '; + } + $s .= "$formatted\n"; } - $limits = ''; + $s .= "\n"; + return $s; + } + + function getIndexField() { + return $this->mSort; + } + + /** + * A navigation bar with images + */ + function getNavigationBar() { + global $wgStylePath; + $path = "$wgStylePath/common/images"; + $labels = array( + 'first' => 'table_pager_first', + 'prev' => 'table_pager_prev', + 'next' => 'table_pager_next', + 'last' => 'table_pager_last', + ); + $images = array( + 'first' => 'arrow_first_25.png', + 'prev' => 'arrow_left_25.png', + 'next' => 'arrow_right_25.png', + 'last' => 'arrow_last_25.png', + ); + $disabledImages = array( + 'first' => 'arrow_disabled_first_25.png', + 'prev' => 'arrow_disabled_left_25.png', + 'next' => 'arrow_disabled_right_25.png', + 'last' => 'arrow_disabled_last_25.png', + ); + + $linkTexts = array(); + $disabledTexts = array(); + foreach ( $labels as $type => $label ) { + $msgLabel = wfMsgHtml( $label ); + $linkTexts[$type] = "\"$msgLabel\"/
$msgLabel"; + $disabledTexts[$type] = "\"$msgLabel\"/
$msgLabel"; + } + $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); + + $s = ''; + $cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"'; + foreach ( $labels as $type => $label ) { + $s .= "\n"; + } + $s .= '
{$links[$type]}
'; + return $s; + } + + /** + * Get a "; foreach ( $this->mLimitsShown as $limit ) { - if ( $limits !== '' ) { - $limits .= ' | '; - } - if ( $this->mIsBackwards ) { - $offset = $this->mPastTheEndIndex; - } else { - $offset = $this->mOffset; - } - $limits .= $this->makeLink( $wgLang->formatNum($limit), - array('offset' => $offset, 'limit' => $limit ) ); + $selected = $limit == $this->mLimit ? 'selected="selected"' : ''; + $formattedLimit = $wgLang->formatNum( $limit ); + $s .= "\n"; + } + $s .= ""; + return $s; + } + /** + * Get elements for use in a method="get" form. + * Resubmits all defined elements of the $_GET array, except for a + * blacklist, passed in the $blacklist parameter. + */ + function getHiddenFields( $blacklist = array() ) { + $blacklist = (array)$blacklist; + $query = $_GET; + foreach ( $blacklist as $name ) { + unset( $query[$name] ); } - - $this->mNavigationBar = "($latestText | $earliestText) " . wfMsgHtml("viewprevnext", $prevText, $nextText, $limits); - return $this->mNavigationBar; + $s = ''; + foreach ( $query as $name => $value ) { + $encName = htmlspecialchars( $name ); + $encValue = htmlspecialchars( $value ); + $s .= "\n"; + } + return $s; } -} + /** + * Get a form containing a limit selection dropdown + */ + function getLimitForm() { + # Make the select with some explanatory text + $url = $this->getTitle()->escapeLocalURL(); + $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' ); + return + "
" . + wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) . + "\n\n" . + $this->getHiddenFields( 'limit' ) . + "
\n"; + } + + /** + * Return true if the named field should be sortable by the UI, false otherwise + * @param string $field + */ + abstract function isFieldSortable( $field ); + + /** + * Format a table cell. The return value should be HTML, but use an empty string + * not   for empty cells. Do not include the and . + * + * @param string $name The database field name + * @param string $value The value retrieved from the database + * + * The current result row is available as $this->mCurrentRow, in case you need + * more context. + */ + abstract function formatValue( $name, $value ); + + /** + * The database field name used as a default sort order + */ + abstract function getDefaultSort(); + + /** + * An array mapping database field names to a textual description of the field + * name, for use in the table header. The description should be plain text, it + * will be HTML-escaped later. + */ + abstract function getFieldNames(); +} ?> diff --git a/includes/SpecialImagelist.php b/includes/SpecialImagelist.php index e456abf5da..eff5a1c9cf 100644 --- a/includes/SpecialImagelist.php +++ b/includes/SpecialImagelist.php @@ -11,111 +11,148 @@ function wfSpecialImagelist() { global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgMiserMode; - $sort = $wgRequest->getVal( 'sort' ); - $wpIlMatch = $wgRequest->getText( 'wpIlMatch' ); - $dbr =& wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $sql = "SELECT img_size,img_name,img_user,img_user_text," . - "img_description,img_timestamp FROM $image"; - - if ( !$wgMiserMode && !empty( $wpIlMatch ) ) { - $nt = Title::newFromUrl( $wpIlMatch ); - if($nt ) { - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( "%", "\\%", $m ); - $m = str_replace( "_", "\\_", $m ); - $sql .= " WHERE LCASE(img_name) LIKE '%{$m}%'"; + $pager = new ImageListPager; + + $limit = $pager->getForm(); + $body = $pager->getBody(); + $nav = $pager->getNavigationBar(); + $wgOut->addHTML( " + $limit +
+ $body + $nav" ); +} + +class ImageListPager extends TablePager { + var $mFieldNames = null; + var $mMessages = array(); + var $mQueryConds = array(); + + function __construct() { + global $wgRequest, $wgMiserMode; + if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { + $this->mDefaultDirection = true; + } else { + $this->mDefaultDirection = false; + } + $search = $wgRequest->getText( 'ilsearch' ); + if ( $search != '' && !$wgMiserMode ) { + $nt = Title::newFromUrl( $search ); + if( $nt ) { + $dbr =& wfGetDB( DB_SLAVE ); + $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); + $m = str_replace( "%", "\\%", $m ); + $m = str_replace( "_", "\\_", $m ); + $this->mQueryConds = array( "LCASE(img_name) LIKE '%{$m}%'" ); + } } - } - if ( "bysize" == $sort ) { - $sql .= " ORDER BY img_size DESC"; - } else if ( "byname" == $sort ) { - $sql .= " ORDER BY img_name"; - } else { - $sort = "bydate"; - $sql .= " ORDER BY img_timestamp DESC"; + parent::__construct(); } - list( $limit, $offset ) = wfCheckLimits( 50 ); - $lt = $wgLang->formatNum( "${limit}" ); - $sql .= " LIMIT {$limit}"; - - $wgOut->addWikiText( wfMsg( 'imglegend' ) ); - $wgOut->addHTML( wfMsgExt( 'imagelisttext', array('parse'), $lt, wfMsg( $sort ) ) ); - - $sk = $wgUser->getSkin(); - $titleObj = Title::makeTitle( NS_SPECIAL, "Imagelist" ); - $action = $titleObj->escapeLocalURL( "sort={$sort}&limit={$limit}" ); - - if ( !$wgMiserMode ) { - $wgOut->addHTML( "
" . - wfElement( 'input', - array( - 'type' => 'text', - 'size' => '20', - 'name' => 'wpIlMatch', - 'value' => $wpIlMatch, )) . - wfElement( 'input', - array( - 'type' => 'submit', - 'name' => 'wpIlSubmit', - 'value' => wfMsg( 'ilsubmit'), )) . - '
' ); + function getFieldNames() { + if ( !$this->mFieldNames ) { + $this->mFieldNames = array( + 'links' => '', + 'img_timestamp' => wfMsg( 'imagelist_date' ), + 'img_name' => wfMsg( 'imagelist_name' ), + 'img_user_text' => wfMsg( 'imagelist_user' ), + 'img_size' => wfMsg( 'imagelist_size' ), + 'img_description' => wfMsg( 'imagelist_description' ), + ); + } + return $this->mFieldNames; } - $here = Title::makeTitle( NS_SPECIAL, 'Imagelist' ); + function isFieldSortable( $field ) { + static $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); + return in_array( $field, $sortable ); + } - foreach ( array( 'byname', 'bysize', 'bydate') as $sorttype ) { - $urls = null; - foreach ( array( 50, 100, 250, 500 ) as $num ) { - $urls[] = $sk->makeKnownLinkObj( $here, $wgLang->formatNum( $num ), - "sort={$sorttype}&limit={$num}&wpIlMatch=" . urlencode( $wpIlMatch ) ); - } - $sortlinks[] = wfMsgExt( - 'showlast', - array( 'parseinline', 'replaceafter' ), - implode($urls, ' | '), - wfMsgExt( $sorttype, array('escape') ) + function getQueryInfo() { + $fields = $this->getFieldNames(); + unset( $fields['links'] ); + $fields = array_keys( $fields ); + $fields[] = 'img_user'; + return array( + 'tables' => 'image', + 'fields' => $fields, + 'conds' => $this->mQueryConds ); } - $wgOut->addHTML( implode( $sortlinks, "
\n") . "\n\n
" ); - // lines - $wgOut->addHTML( '

' ); - $res = $dbr->query( $sql, "wfSpecialImagelist" ); + function getDefaultSort() { + return 'img_timestamp'; + } - while ( $s = $dbr->fetchObject( $res ) ) { - $name = $s->img_name; - $ut = $s->img_user_text; - if ( 0 == $s->img_user ) { - $ul = $ut; - } else { - $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut ); + function getStartBody() { + # Do a link batch query for user pages + if ( $this->mResult->numRows() ) { + $lb = new LinkBatch; + $this->mResult->seek( 0 ); + while ( $row = $this->mResult->fetchObject() ) { + if ( $row->img_user ) { + $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) ); + } + } + $lb->execute(); } - $dirmark = $wgContLang->getDirMark(); // to keep text in correct direction - - $ilink = "" . strtr(htmlspecialchars( $name ), '_', ' ') . ""; - - $nb = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), - $wgLang->formatNum( $s->img_size ) ); - - $desc = $sk->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), - wfMsg( 'imgdesc' ) ); + # Cache messages used in each row + $this->mMessages['imgdesc'] = wfMsgHtml( 'imgdesc' ); + $this->mMessages['imgfile'] = wfMsgHtml( 'imgfile' ); + + return parent::getStartBody(); + } - $date = $wgLang->timeanddate( $s->img_timestamp, true ); - $comment = $sk->commentBlock( $s->img_description ); + function formatValue( $field, $value ) { + global $wgLang; + switch ( $field ) { + case 'links': + $name = $this->mCurrentRow->img_name; + $ilink = "" . $this->mMessages['imgfile'] . ""; + $desc = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), + $this->mMessages['imgdesc'] ); + return "$desc | $ilink"; + case 'img_timestamp': + return $wgLang->timeanddate( $value, true ); + case 'img_name': + return htmlspecialchars( $value ); + case 'img_user_text': + if ( $this->mCurrentRow->img_user ) { + $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ), + htmlspecialchars( $value ) ); + } else { + $link = htmlspecialchars( $value ); + } + return $link; + case 'img_size': + return $wgLang->formatNum( $value ); + case 'img_description': + return $this->getSkin()->commentBlock( $value ); + } + } - $l = "({$desc}) {$dirmark}{$ilink} . . {$dirmark}{$nb} . . {$dirmark}{$ul}". - " . . {$dirmark}{$date} . . {$dirmark}{$comment}
\n"; - $wgOut->addHTML( $l ); + function getForm() { + global $wgRequest, $wgMiserMode; + $url = $this->getTitle()->escapeLocalURL(); + $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' ); + $msgSearch = wfMsgHtml( 'imagelist_search_for' ); + $search = $wgRequest->getText( 'ilsearch' ); + $encSearch = htmlspecialchars( $search ); + $s = "

\n" . + wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ); + if ( !$wgMiserMode ) { + $s .= "
\n" . $msgSearch . + "
\n"; + } + $s .= " \n" . + $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . + "
\n"; + return $s; } - $dbr->freeResult( $res ); - $wgOut->addHTML( '

' ); } ?> diff --git a/languages/MessagesEn.php b/languages/MessagesEn.php index ae818d8a5c..17571e10ee 100644 --- a/languages/MessagesEn.php +++ b/languages/MessagesEn.php @@ -1162,6 +1162,7 @@ created and by whom, and anything else you may know about it. If this is an imag 'bysize' => 'by size', 'imgdelete' => 'del', 'imgdesc' => 'desc', +'imgfile' => 'file', 'imglegend' => 'Legend: (desc) = show/edit file description.', 'imghistory' => 'File history', 'revertimg' => 'rev', @@ -1180,6 +1181,12 @@ this old version, (rev) = revert to this old version. 'noimage' => 'No file by this name exists, you can $1.', 'noimage-linktext' => 'upload it', 'uploadnewversion-linktext' => 'Upload a new version of this file', +'imagelist_date' => 'Date', +'imagelist_name' => 'Name', +'imagelist_user' => 'User', +'imagelist_size' => 'Size (bytes)', +'imagelist_description' => 'Description', +'imagelist_search_for' => 'Search for image name:', # Mime search # @@ -2358,6 +2365,17 @@ Please confirm that really want to recreate this page.', * Italiano|it * Nederlands|nl", +# Table pager +'ascending_abbrev' => 'asc', +'descending_abbrev' => 'desc', +'table_pager_next' => 'Next page', +'table_pager_prev' => 'Previous page', +'table_pager_first' => 'First page', +'table_pager_last' => 'Last page', +'table_pager_limit' => 'Show $1 items per page', +'table_pager_limit_submit' => 'Go', +'table_pager_empty' => 'No results', + ); ?>